﻿/* EntitiesToDTOs. Copyright (c) 2011. Fabian Fernandez.
 * http://entitiestodtos.codeplex.com
 * Licensed by Common Development and Distribution License (CDDL).
 * http://entitiestodtos.codeplex.com/license
 * Fabian Fernandez. 
 * http://www.linkedin.com/in/fabianfernandezb/en
 * */
using EnvDTE;
using EnvDTE80;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using EntitiesToDTOs.Properties;
using System.Collections;
using EntitiesToDTOs.Domain;
using System.IO;

namespace EntitiesToDTOs.Helpers
{
    /// <summary>
    /// Helps in the tasks relative to Visual Studio manipulation (Solution, Projects, ProjectItems, etc.).
    /// </summary>
    internal class VisualStudioHelper
    {
        /// <summary>
        /// List of NOT supported project types.
        /// </summary>
        private static List<string> NotSupportedProjectTypes
        {
            get
            {
                if (_notSupportedProjectTypes == null)
                {
                    _notSupportedProjectTypes = new List<string>();
                    _notSupportedProjectTypes.Add(Resources.VSGuidProjectSetup);
                    _notSupportedProjectTypes.Add(Resources.VSGuidProjectJSharp);
                    _notSupportedProjectTypes.Add(Resources.VSGuidProjectDatabase);
                    _notSupportedProjectTypes.Add(Resources.VSGuidProjectDatabaseOther);
                    _notSupportedProjectTypes.Add(Resources.VSGuidProjectDeploymentCab);
                    _notSupportedProjectTypes.Add(Resources.VSGuidProjectDeploymentMergeModule);
                    _notSupportedProjectTypes.Add(Resources.VSGuidProjectDeploymentSetup);
                    _notSupportedProjectTypes.Add(Resources.VSGuidProjectDeploymentSmartDeviceCab);
                    _notSupportedProjectTypes.Add(Resources.VSGuidProjectSharepointVB);
                    _notSupportedProjectTypes.Add(Resources.VSGuidProjectSmartDeviceVB);
                    _notSupportedProjectTypes.Add(Resources.VSGuidProjectWindowsCPlusPlus);
                    _notSupportedProjectTypes.Add(Resources.VSGuidProjectWindowsVB);
                    _notSupportedProjectTypes.Add(Resources.VSGuidProjectWorkflowVB);
                }

                return _notSupportedProjectTypes;
            }
        }
        private static List<string> _notSupportedProjectTypes = null;



        /// <summary>
        /// Checks if a Solution is open.
        /// </summary>
        /// <returns></returns>
        public static bool IsSolutionOpen(DTE2 application)
        {
            if (application.Solution == null
                || application.Solution.IsOpen == false
                || string.IsNullOrWhiteSpace(application.Solution.FullName))
            {
                return false;
            }

            return true;
        }

        /// <summary>
        /// Gets the Default Namespace of the provided Project.
        /// </summary>
        /// <param name="projectItem">Project to get the Default Namespace.</param>
        /// <returns></returns>
        public static string GetProjectDefaultNamespace(Project projectItem)
        {
            return projectItem.Properties.Item(Resources.Project_DefaultNamespace)
                .Value.ToString();
        }

        /// <summary>
        /// Gets all items of the projects of a provided Solution.
        /// </summary>
        /// <param name="solution"></param>
        /// <returns></returns>
        public static List<ProjectItem> GetSolutionProjectItems(Solution solution)
        {
            var result = new List<ProjectItem>();

            foreach (Project item in solution.Projects)
            {
                if (item.ProjectItems != null)
                {
                    result.AddRange(VisualStudioHelper.GetProjectItems(item.ProjectItems));
                }
            }

            return result;
        }

        /// <summary>
        /// Gets the items of the provided ProjectItems collection.
        /// </summary>
        /// <param name="itemsCollection"></param>
        /// <returns></returns>
        private static List<ProjectItem> GetProjectItems(ProjectItems itemsCollection)
        {
            var result = new List<ProjectItem>();

            foreach (ProjectItem item in itemsCollection)
            {
                result.Add(item);

                if (item.SubProject != null && item.SubProject.ProjectItems != null)
                {
                    result.AddRange(VisualStudioHelper.GetProjectItems(item.SubProject.ProjectItems));
                }

                if (item.ProjectItems != null)
                {
                    result.AddRange(VisualStudioHelper.GetProjectItems(item.ProjectItems));
                }
            }

            return result;
        }

        /// <summary>
        /// Gets the Projects of the provided Solution.
        /// </summary>
        /// <param name="solution">Solution to get the Projects from.</param>
        /// <returns></returns>
        public static List<Project> GetProjectsFromSolution(Solution solution)
        {
            var result = new List<Project>();

            IEnumerator item = solution.Projects.GetEnumerator();

            while (item.MoveNext())
            {
                var project = (item.Current as Project);

                if (project == null)
                {
                    continue;
                }

                if (project.Kind == ProjectKinds.vsProjectKindSolutionFolder)
                {
                    result.AddRange(VisualStudioHelper.GetSolutionFolderProjects(project));
                }
                else if (VisualStudioHelper.NotSupportedProjectTypes.Contains(project.Kind) == false)
                {
                    result.Add(project);
                }
            }

            return result.OrderBy(i => i.Name).ToList();
        }

        /// <summary>
        /// Gets the Projects of a a Solution Folder.
        /// </summary>
        /// <param name="solutionFolder">Solution Folder to get projects from.</param>
        /// <returns></returns>
        private static IEnumerable<Project> GetSolutionFolderProjects(Project solutionFolder)
        {
            var result = new List<Project>();

            IEnumerator item = solutionFolder.ProjectItems.GetEnumerator();

            while (item.MoveNext())
            {
                var subProject = (item.Current as ProjectItem).SubProject;

                if (subProject == null)
                {
                    continue;
                }

                if (subProject.Kind == ProjectKinds.vsProjectKindSolutionFolder)
                {
                    result.AddRange(VisualStudioHelper.GetSolutionFolderProjects(subProject));
                }
                else if (VisualStudioHelper.NotSupportedProjectTypes.Contains(subProject.Kind) == false)
                {
                    result.Add(subProject);
                }
            }

            return result;
        }

        /// <summary>
        /// Adds the provided Reference to the received Project.
        /// </summary>
        /// <param name="targetProject">Project to add the Reference.</param>
        /// <param name="referenceIdentity">Reference Identity (Name).</param>
        /// <param name="browseUrl">URL of the Reference (same as the Identity if it is in the GAC).</param>
        public static void AddReferenceToProject(Project targetProject,
            string referenceIdentity, string browseUrl)
        {
            string path = string.Empty;

            if (browseUrl.StartsWith(referenceIdentity) == false)
            {
                // It is a path
                path = browseUrl;
            }

            if (targetProject.Object is VSLangProj.VSProject)
            {
                VSLangProj.VSProject vsproject = (VSLangProj.VSProject)targetProject.Object;
                VSLangProj.Reference reference = null;

                try
                {
                    reference = vsproject.References.Find(referenceIdentity);
                }
                catch (Exception)
                {
                    // It failed to find one, so it must not exist.
                }

                if (reference == null)
                {
                    try
                    {
                        if (string.IsNullOrWhiteSpace(path))
                        {
                            vsproject.References.Add(browseUrl);
                        }
                        else
                        {
                            vsproject.References.Add(path);
                        }
                    }
                    catch (Exception)
                    {
                        WarningsPool.AddWarning(
                            string.Format(Resources.Warning_ManuallyAddReference, referenceIdentity, targetProject.Name));
                    }
                }
                else
                {
                    // Reference already exists.
                }
            }
            else if (targetProject.Object is VsWebSite.VSWebSite)
            {
                VsWebSite.VSWebSite vswebsite = (VsWebSite.VSWebSite)targetProject.Object;
                VsWebSite.AssemblyReference reference = null;

                try
                {
                    foreach (VsWebSite.AssemblyReference r in vswebsite.References)
                    {
                        if (r.Name == referenceIdentity)
                        {
                            reference = r;
                            break;
                        }
                    }
                }
                catch (Exception)
                {
                    // It failed to find one, so it must not exist.
                }

                if (reference == null)
                {
                    if (string.IsNullOrWhiteSpace(path))
                    {
                        vswebsite.References.AddFromGAC(browseUrl);
                    }
                    else
                    {
                        vswebsite.References.AddFromFile(path);
                    }
                }
                else
                {
                    // Reference already exists.
                }
            }
            else
            {
                WarningsPool.AddWarning(
                    string.Format(Resources.Warning_ManuallyAddReference, referenceIdentity, targetProject.Name));
            }
        }

        /// <summary>
        /// Adds the provided Reference to the received Project.
        /// </summary>
        /// <param name="targetProject">Project to add the Reference.</param>
        /// <param name="referencedProject">Project to be referenced.</param>
        public static void AddReferenceToProject(Project targetProject, Project referencedProject)
        {
            if (targetProject.Object is VSLangProj.VSProject)
            {
                VSLangProj.VSProject vsproject = (VSLangProj.VSProject)targetProject.Object;
                VSLangProj.Reference reference = null;

                try
                {
                    reference = vsproject.References.Find(referencedProject.Name);
                }
                catch (Exception)
                {
                    // It failed to find one, so it must not exist.
                }

                if (reference == null)
                {
                    try
                    {
                        vsproject.References.AddProject(referencedProject);
                    }
                    catch (Exception)
                    {
                        WarningsPool.AddWarning(
                            string.Format(Resources.Warning_ManuallyAddReference, referencedProject.Name, targetProject.Name));
                    }
                }
                else
                {
                    // Reference already exists.
                }
            }
            else if (targetProject.Object is VsWebSite.VSWebSite)
            {
                VsWebSite.VSWebSite vswebsite = (VsWebSite.VSWebSite)targetProject.Object;
                VsWebSite.AssemblyReference reference = null;

                try
                {
                    foreach (VsWebSite.AssemblyReference r in vswebsite.References)
                    {
                        if (r.Name == referencedProject.Name)
                        {
                            reference = r;
                            break;
                        }
                    }
                }
                catch (Exception)
                {
                    // It failed to find one, so it must not exist.
                }

                if (reference == null)
                {
                    try
                    {
                        vswebsite.References.AddFromProject(referencedProject);
                    }
                    catch (Exception)
                    {
                        WarningsPool.AddWarning(
                            string.Format(Resources.Warning_ManuallyAddReference, referencedProject.Name, targetProject.Name));
                    }
                }
                else
                {
                    // Reference already exists.
                }
            }
            else
            {
                WarningsPool.AddWarning(
                    string.Format(Resources.Warning_ManuallyAddReference, referencedProject.Name, targetProject.Name));
            }
        }

        /// <summary>
        /// Adds imports to a ProjectItem.
        /// </summary>
        /// <param name="sourceFileItem">ProjectItem to add the imports.</param>
        /// <param name="importList">List of imports to add.</param>
        public static void AddImportsToSourceCode(ref ProjectItem sourceFileItem, List<SourceCodeImport> importList)
        {
            // Cast to FileCodeModel2 for AddImport method
            var objFileCodeModel2 = (sourceFileItem.FileCodeModel as FileCodeModel2);

            // Add imports (using ...)
            foreach (SourceCodeImport import in importList)
            {
                objFileCodeModel2.AddImport(import.ImportNamespace);
            }
        }

        /// <summary>
        /// Generates the specified Source File in the received Project with the options 
        /// provided and gets the Namespace ready to add code in it.
        /// </summary>
        /// <param name="targetProject">Project where the Source File is going to be placed.</param>
        /// <param name="sourceFileName">Source File name to use.</param>
        /// <param name="sourceFileHeaderComment">Source File Header Comment (optional).</param>
        /// <param name="sourceNamespace">Namespace used in the Source File.</param>
        /// <param name="isServiceReady">Specifies if it is Service-Ready (serialization is going to be used).</param>
        /// <param name="sourceFileItem">(out parameter) Source File ProjectItem.</param>
        /// <returns></returns>
        public static CodeNamespace GenerateSourceAndGetNamespace(Project targetProject,
            string sourceFileName, string sourceFileHeaderComment, string sourceNamespace,
            bool isServiceReady, out ProjectItem sourceFileItem)
        {
            // Validate Source File Name
            if (sourceFileName.EndsWith(Resources.CSharpFileExtension) == false)
            {
                sourceFileName += Resources.CSharpFileExtension;
            }

            // Source File
            sourceFileItem = null;

            #region If Source File exists in the Project, clear it and get the reference
            foreach (ProjectItem projItem in targetProject.ProjectItems)
            {
                if (sourceFileName == projItem.Properties.Item(Resources.ProjectItem_FileName).Value.ToString())
                {
                    // Source File already exists
                    sourceFileItem = projItem;

                    if (sourceFileItem.FileCodeModel.CodeElements != null
                        && sourceFileItem.FileCodeModel.CodeElements.Count > 0)
                    {
                        // Clear Source File

                        CodeElement firstElement = sourceFileItem.FileCodeModel.CodeElements.Item(1);

                        CodeElement lastElement = sourceFileItem.FileCodeModel.CodeElements.Item(
                            sourceFileItem.FileCodeModel.CodeElements.Count);

                        EditPoint startPoint = firstElement.StartPoint.CreateEditPoint();
                        EditPoint endPoint = lastElement.EndPoint.CreateEditPoint();

                        while (startPoint.AtStartOfDocument != true)
                        {
                            startPoint.LineUp();
                        }

                        while (endPoint.AtEndOfDocument != true)
                        {
                            endPoint.LineDown();
                        }

                        startPoint.Delete(endPoint);
                    }

                    break;
                }
            }
            #endregion

            #region If Source File NOT exists in the Project, create it and get the reference
            if (sourceFileItem == null)
            {
                // New Source File

                // Get the Target Project folder path
                string targetProjectFolderPath = targetProject.FullName;
                targetProjectFolderPath = targetProjectFolderPath.Remove(
                    targetProjectFolderPath.LastIndexOf(Resources.Backslash) + 1);

                // Check if the new Source File already exists in the File System (and it is not added to the Solution)
                if (File.Exists(targetProjectFolderPath + sourceFileName))
                {
                    // Rename the existent Source File
                    string backupSourceFileName = (sourceFileName + Resources.BackupFileExtension);
                    File.Move((targetProjectFolderPath + sourceFileName), 
                        (targetProjectFolderPath + backupSourceFileName));

                    // Add Warning
                    WarningsPool.AddWarning(string.Format(Resources.Warning_SourceFileAlreadyExists,
                        sourceFileName, backupSourceFileName));
                }

                // Add Source File to Project
                sourceFileItem = targetProject.ProjectItems.AddFromFileCopy(Resources.TemplateClassPath);

                // Set Source File Name
                sourceFileItem.Properties.Item(Resources.ProjectItem_FileName).Value = sourceFileName;
            }
            #endregion

            #region Generate imports
            var importList = new List<SourceCodeImport>();
            importList.Add(new SourceCodeImport(Resources.NamespaceSystem));
            importList.Add(new SourceCodeImport(Resources.NamespaceSystemCollectionsGeneric));
            importList.Add(new SourceCodeImport(Resources.NamespaceSystemText));

            if (isServiceReady)
            {
                importList.Add(new SourceCodeImport(Resources.NamespaceSystemRuntimeSerialization));
            }

            importList = importList.OrderBy(d => d.ImportNamespace).ToList();
            #endregion Generate imports

            // Add imports to the Source Code
            VisualStudioHelper.AddImportsToSourceCode(ref sourceFileItem, importList);

            // Get Source File code start
            EditPoint objEditPoint = sourceFileItem.FileCodeModel.CodeElements.Item(1).StartPoint.CreateEditPoint();
            objEditPoint.StartOfDocument();

            // Header Comment
            if (string.IsNullOrWhiteSpace(sourceFileHeaderComment) == false)
            {
                sourceFileHeaderComment = (Environment.NewLine + sourceFileHeaderComment + Environment.NewLine);

                objEditPoint.Insert(
                    string.Format(Resources.CSharpCommentMultiline, sourceFileHeaderComment) +
                    Environment.NewLine);
            }

            // EntitiesToDTOs signature
            objEditPoint.Insert(Resources.CSharpCommentSingleLine);
            objEditPoint.Insert(string.Format(Resources.EntitiesToDTOsSignature, AssemblyHelper.Version));
            objEditPoint.Insert(Environment.NewLine);

            // Add timestamp
            objEditPoint.Insert(Resources.CSharpCommentSingleLine +
                string.Format(Resources.SourceFileTimestamp,
                    DateTime.Now.ToShortDateString(), DateTime.Now.ToLongTimeString()) +
                Environment.NewLine);

            // Add blank line before Source File Namespace
            objEditPoint.EndOfDocument();
            objEditPoint.Insert(Environment.NewLine);

            // Add Namespace
            CodeNamespace objNamespace = sourceFileItem.FileCodeModel
                .AddNamespace(sourceNamespace, AppConstants.PLACE_AT_THE_END);

            return objNamespace;
        }
    }
}